package com.socialsoftware.util;

import android.content.Context;
import android.content.SharedPreferences;
import android.os.Handler;
import android.os.Looper;
import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyProperties;
import android.text.TextUtils;
import android.util.Log;

import androidx.security.crypto.EncryptedSharedPreferences;
import androidx.security.crypto.MasterKey;

import com.google.gson.Gson;
import com.socialsoftware.model.User;

import java.io.IOException;
import java.security.GeneralSecurityException;
import java.util.concurrent.TimeUnit;

/**
 * SessionManager handles user authentication state and secure storage of user data.
 * Uses EncryptedSharedPreferences for secure storage.
 */
public class SessionManager {
    
    private static final String TAG = "SessionManager";
    
    // Shared preferences file name
    private static final String PREF_NAME = "SocialSoftwareSecurePrefs";
    
    // Shared preferences keys
    private static final String KEY_IS_LOGGED_IN = "isLoggedIn";
    private static final String KEY_USER_DATA = "userData";
    private static final String KEY_AUTH_TOKEN = "authToken";
    private static final String KEY_TOKEN_EXPIRY = "tokenExpiry";
    
    // JWT token refresh threshold (in milliseconds)
    private static final long TOKEN_REFRESH_THRESHOLD = TimeUnit.MINUTES.toMillis(30); // 30 minutes before expiry
    
    private SharedPreferences sharedPreferences;
    private SharedPreferences.Editor editor;
    private Context context;
    private Handler tokenRefreshHandler;
    private Runnable tokenRefreshRunnable;
    
    private static volatile SessionManager INSTANCE;
    
    public SessionManager(Context context) {
        this.context = context.getApplicationContext(); // Always use application context
        this.tokenRefreshHandler = new Handler(Looper.getMainLooper());
        
        try {
            // Create or retrieve the Master Key for encryption/decryption
            KeyGenParameterSpec spec = new KeyGenParameterSpec.Builder(
                    MasterKey.DEFAULT_MASTER_KEY_ALIAS,
                    KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
                    .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
                    .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
                    .setKeySize(MasterKey.DEFAULT_AES_GCM_MASTER_KEY_SIZE)
                    .build();
            
            MasterKey masterKey = new MasterKey.Builder(this.context)
                    .setKeyGenParameterSpec(spec)
                    .build();
            
            // Create the EncryptedSharedPreferences
            sharedPreferences = EncryptedSharedPreferences.create(
                    this.context,
                    PREF_NAME,
                    masterKey,
                    EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
                    EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
            );
            
            editor = sharedPreferences.edit();
        } catch (GeneralSecurityException | IOException e) {
            Log.e(TAG, "Error initializing encrypted shared preferences", e);
            // Fallback to regular SharedPreferences if encryption fails
            sharedPreferences = this.context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE);
            editor = sharedPreferences.edit();
        }
    }
    
    public static synchronized SessionManager getInstance(Context context) {
        if (INSTANCE == null) {
            INSTANCE = new SessionManager(context.getApplicationContext());
        }
        return INSTANCE;
    }
    
    /**
     * Get the application context
     */
    public Context getContext() {
        return context;
    }
    
    /**
     * Save user login session data
     */
    public void createLoginSession(User user, String authToken) {
        editor.putBoolean(KEY_IS_LOGGED_IN, true);
        editor.putString(KEY_AUTH_TOKEN, authToken);
        
        // Calculate and store token expiry time if possible
        long expiryTime = JwtTokenUtil.getTokenExpirationTime(authToken);
        if (expiryTime > 0) {
            long expiryTimeMillis = System.currentTimeMillis() + expiryTime;
            editor.putLong(KEY_TOKEN_EXPIRY, expiryTimeMillis);
            
            // Schedule token refresh before expiry
            scheduleTokenRefresh(expiryTime);
        }
        
        // Convert user object to JSON string
        Gson gson = new Gson();
        String userJson = gson.toJson(user);
        editor.putString(KEY_USER_DATA, userJson);
        
        // Commit changes
        editor.apply();
    }
    
    /**
     * Get stored user data
     */
    public User getUser() {
        String userJson = sharedPreferences.getString(KEY_USER_DATA, null);
        if (userJson != null) {
            Gson gson = new Gson();
            return gson.fromJson(userJson, User.class);
        }
        return null;
    }
    
    /**
     * Get stored auth token
     */
    public String getAuthToken() {
        return sharedPreferences.getString(KEY_AUTH_TOKEN, null);
    }
    
    /**
     * Check if token is valid or needs refresh
     */
    public boolean isTokenValid() {
        String token = getAuthToken();
        if (token == null) {
            return false;
        }
        
        // Check if token is expired
        return !JwtTokenUtil.isTokenExpired(token);
    }
    
    /**
     * Check if token needs to be refreshed soon
     */
    public boolean isTokenNeedsRefresh() {
        String token = getAuthToken();
        if (token == null) {
            return false;
        }
        
        long timeRemaining = JwtTokenUtil.getTokenExpirationTime(token);
        return timeRemaining > 0 && timeRemaining < TOKEN_REFRESH_THRESHOLD;
    }
    
    /**
     * Schedule token refresh before expiry
     */
    private void scheduleTokenRefresh(long expiryTimeMillis) {
        // Cancel any previously scheduled refresh
        if (tokenRefreshRunnable != null) {
            tokenRefreshHandler.removeCallbacks(tokenRefreshRunnable);
        }
        
        // Schedule a new refresh if expiry time is valid
        if (expiryTimeMillis > TOKEN_REFRESH_THRESHOLD) {
            long refreshDelay = expiryTimeMillis - TOKEN_REFRESH_THRESHOLD;
            
            tokenRefreshRunnable = new Runnable() {
                @Override
                public void run() {
                    // This would trigger token refresh process in a real app
                    // For now, we just log it
                    Log.d(TAG, "Token is approaching expiry - refresh needed");
                    
                    // TODO: Implement actual token refresh via API call
                    // In a real app, this would call an API endpoint to refresh the token
                }
            };
            
            tokenRefreshHandler.postDelayed(tokenRefreshRunnable, refreshDelay);
            Log.d(TAG, "Scheduled token refresh in " + (refreshDelay / 1000 / 60) + " minutes");
        }
    }
    
    /**
     * Check login status
     */
    public boolean isLoggedIn() {
        boolean isLoggedIn = sharedPreferences.getBoolean(KEY_IS_LOGGED_IN, false);
        
        // Also check if token is still valid
        if (isLoggedIn) {
            String token = getAuthToken();
            if (token != null && JwtTokenUtil.isTokenExpired(token)) {
                // Token has expired, but we haven't logged out yet
                // Consider the user as logged out
                Log.d(TAG, "Token has expired, considering user as logged out");
                return false;
            }
        }
        
        return isLoggedIn;
    }
    
    /**
     * 检查用户会话是否有效
     * 验证登录状态、令牌有效性和用户数据完整性
     * @return 如果会话有效返回true，否则返回false
     */
    public boolean isSessionValid() {
        // 检查登录状态
        if (!sharedPreferences.getBoolean(KEY_IS_LOGGED_IN, false)) {
            Log.d(TAG, "会话验证失败: 用户未登录");
            return false;
        }
        
        // 检查令牌是否存在且有效
        String token = getAuthToken();
        if (token == null) {
            Log.d(TAG, "会话验证失败: 令牌为空");
            return false;
        }
        
        if (JwtTokenUtil.isTokenExpired(token)) {
            Log.d(TAG, "会话验证失败: 令牌已过期");
            return false;
        }
        
        // 检查用户数据是否完整
        User user = getUser();
        if (user == null || TextUtils.isEmpty(user.getId()) || TextUtils.isEmpty(user.getUsername())) {
            Log.d(TAG, "会话验证失败: 用户数据无效");
            return false;
        }
        
        return true;
    }
    
    /**
     * 获取当前登录用户的ID
     * @return 用户ID，如果未登录则返回null
     */
    public Long getCurrentUserId() {
        User user = getUser();
        if (user != null && !TextUtils.isEmpty(user.getId())) {
            try {
                // 处理可能包含小数点的ID (例如 "2.0")
                double doubleId = Double.parseDouble(user.getId());
                return (long) doubleId;
            } catch (NumberFormatException e) {
                Log.e(TAG, "无法解析用户ID: " + user.getId(), e);
            }
        }
        return null;
    }
    
    /**
     * 获取当前登录用户的用户名
     * @return 用户名，如果未登录则返回null
     */
    public String getUserName() {
        User user = getUser();
        return user != null ? user.getUsername() : null;
    }
    
    /**
     * Logout user - Clear session details
     */
    public void logout() {
        // Cancel any scheduled token refresh
        if (tokenRefreshRunnable != null) {
            tokenRefreshHandler.removeCallbacks(tokenRefreshRunnable);
            tokenRefreshRunnable = null;
        }
        
        editor.clear();
        editor.apply();
    }
    
    /**
     * Update user data
     */
    public void updateUserData(User user) {
        Gson gson = new Gson();
        String userJson = gson.toJson(user);
        editor.putString(KEY_USER_DATA, userJson);
        editor.apply();
    }
    
    /**
     * Update auth token
     */
    public void updateAuthToken(String authToken) {
        editor.putString(KEY_AUTH_TOKEN, authToken);
        
        // Calculate and store token expiry time if possible
        long expiryTime = JwtTokenUtil.getTokenExpirationTime(authToken);
        if (expiryTime > 0) {
            long expiryTimeMillis = System.currentTimeMillis() + expiryTime;
            editor.putLong(KEY_TOKEN_EXPIRY, expiryTimeMillis);
            
            // Schedule token refresh before expiry
            scheduleTokenRefresh(expiryTime);
        }
        
        editor.apply();
    }
} 